﻿using System.Collections.Concurrent;
using System.Linq.Expressions;
using System.Reflection;
using Dyao.Book.Core;
using Foundation.Core;

namespace Dyao.Book.Repository.Shared
{

    public class DefaultEntityPropertyCompleteProvider : EntityPropertyCompleteProvider, ITransientDependency
    {

        static List<(string[], Type, string)> _insertPropertiesInfo = new List<(string[], Type, string)>()
        {
            (new[]{"CreateUserId" },typeof(int),nameof(CompletePropertyContext.UserId)),
            (new[]{"CreateUserName" },typeof(string),nameof(CompletePropertyContext.UserName)),
            (new[]{"CreateDateTime" },typeof(DateTime),nameof(CompletePropertyContext.DateTime)),
        };

        static List<(string[], Type, string)> _updatePropertiesInfo = new List<(string[], Type, string)>()
        {
            (new[]{"UpdateUserId" },typeof(int),nameof(CompletePropertyContext.UserId)),
            (new[]{"UpdateUserName" },typeof(string),nameof(CompletePropertyContext.UserName)),
            (new[]{"UpdateDateTime" },typeof(DateTime),nameof(CompletePropertyContext.DateTime)),
        };

        static ConcurrentDictionary<Type, CompleteMemberInfo> _insertDelegate = new ConcurrentDictionary<Type, CompleteMemberInfo>();
        static ConcurrentDictionary<Type, CompleteMemberInfo> _updateDelegate = new ConcurrentDictionary<Type, CompleteMemberInfo>();

        private CompletePropertyContext _context;
        private readonly ICurrentUser _currentUser;

        public DefaultEntityPropertyCompleteProvider(ICurrentUser currentUser)
        {
            this._currentUser = currentUser;
        }

        public override bool HasCreateProperty(Type entityType) => GetInsertInfo(entityType) != null;

        public override IList<MemberInfo> GetCreatePropertyInfos(Type entityType) => GetInsertInfo(entityType)?.Members;

        public override void CompleteCreateProperties<T>(IEnumerable<T> entities)
            where T : class
        {
            var action = (Action<T, CompletePropertyContext>)GetInsertInfo(typeof(T))?.CompleteAction;

            if (action == null) return;

            var context = CreateContext();

            foreach (var entity in entities)
            {
                action.Invoke(entity, context);
            }
        }

        public override bool HasUpdateProperty(Type entityType) => GetUpdateInfo(entityType) != null;

        public override IList<MemberInfo> GetUpdatePropertyInfos(Type entityType) => GetUpdateInfo(entityType)?.Members;

        public override void CompleteUpdateProperties<T>(IEnumerable<T> entities)
            where T : class
        {
            var action = (Action<T, CompletePropertyContext>)GetUpdateInfo(typeof(T))?.CompleteAction;

            if (action == null) return;

            var context = CreateContext();

            foreach (var entity in entities)
            {
                action.Invoke(entity, context);
            }
        }

        protected virtual CompletePropertyContext CreateContext()
        {
            return _context ??= new CompletePropertyContext()
            {
                UserId = Convert.ToInt32(_currentUser.Id),
                UserName = _currentUser.UserName,
                DateTime = DateTime.Now,
            };
        }

        protected CompleteMemberInfo GetInsertInfo(Type entityType) => _insertDelegate.GetOrAdd(entityType, t => CreateComplete(t, _insertPropertiesInfo, false));

        protected CompleteMemberInfo GetUpdateInfo(Type entityType) => _updateDelegate.GetOrAdd(entityType, t => CreateComplete(t, _updatePropertiesInfo, false));

        protected static CompleteMemberInfo CreateComplete(Type type, List<(string[], Type, string)> propertiesInfo, bool isCheckHasValue)
        {
            var actionLlist = new List<Expression>();
            var actionMembers = new List<MemberInfo>();

            var entityParameter = Expression.Parameter(type, "entity");
            var contextParameter = Expression.Parameter(typeof(CompletePropertyContext), "context");

            var ps = type.GetProperties();
            var contextProperies = typeof(CompletePropertyContext).GetProperties();

            foreach (var (entityPropertyNames, properyType, contextPropertyName) in propertiesInfo)
            {
                foreach (var pName in entityPropertyNames)
                {
                    var p = ps.FirstOrDefault(p => p.Name == pName);

                    if (p == null || !p.CanWrite) continue;

                    var underlyingType = Nullable.GetUnderlyingType(p.PropertyType) ?? p.PropertyType;

                    if (underlyingType != properyType) continue;

                    var isNullable = underlyingType != p.PropertyType;

                    Expression contextValue = Expression.Property(contextParameter, contextPropertyName); //context 属性取值 context.xxxx

                    if (isNullable) //可空值类型转换
                    {
                        contextValue = Expression.Convert(contextValue, p.PropertyType);
                    }

                    Expression body = Expression.Assign(Expression.Property(entityParameter, p), contextValue); //赋值表达式

                    if (isCheckHasValue)
                    {
                        Expression condiction; //条件表达式；原属性未赋值时，才进行赋值曹组

                        if (underlyingType == typeof(string))
                        {
                            var method = typeof(string).GetMethod(nameof(string.IsNullOrEmpty), System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);

                            condiction = Expression.Call(null, method, Expression.Property(entityParameter, p));
                        }
                        else
                        {
                            var value = Expression.Constant(underlyingType.IsValueType ? Activator.CreateInstance(underlyingType) : null);
                            if (isNullable)
                            {
                                var hasValue = Expression.IsFalse(Expression.Property(Expression.Property(entityParameter, p), p.PropertyType.GetProperty("HasValue")));

                                condiction = Expression.Equal(Expression.Property(Expression.Property(entityParameter, p), "Value"), value);

                                condiction = Expression.OrElse(hasValue, condiction);
                            }
                            else
                            {
                                condiction = Expression.Equal(Expression.Property(entityParameter, p), value);
                            }
                        }

                        body = Expression.IfThen(condiction, body);
                    }

                    actionLlist.Add(body);
                    actionMembers.Add(p);

                    break;
                }
            }

            if (actionLlist.Count == 0) return null;

            var assignBody = Expression.Block(actionLlist.ToArray());

            var actionType = typeof(Action<,>).MakeGenericType(type, typeof(CompletePropertyContext));
            var result = Expression.Lambda(actionType, assignBody, entityParameter, contextParameter);

            var actioin = result.Compile();

            return new CompleteMemberInfo { Members = actionMembers, CompleteAction = actioin };
        }

        #region inner class

        protected class CompletePropertyContext
        {
            public int UserId { get; set; }

            public string UserName { get; set; }

            public DateTime DateTime { get; set; }
        }

        protected class CompleteMemberInfo
        {
            public IList<MemberInfo> Members { get; set; }

            public Delegate CompleteAction { get; set; }
        }

        #endregion
    }
}
